#include "sd_synchashtable.h"
#include "sd_memory.h"
#include "sd_log.h"

sd_Ret sd_SyncHashtableCreate(sd_SyncHashtable** table, 
						  sd_HashFunc hash_func,
						  sd_HashKeyCompare key_cmp, 
						  size_t hashsize, 
						  size_t bucketNodeSize, 
						  size_t keysize, 
						  size_t elemsize,
						  struct sd_MemoryAllocator* ma)
{
	sd_Ret ret;
	uint32 i;
	if (ma == NULL)
		ma = sd_GetDefaultMemoryAllocator();

	*table = (sd_SyncHashtable*)ma->allocate(sizeof(sd_SyncHashtable)+(hashsize-1)*sizeof(sd_SyncBlist));
	if (*table == NULL)
	{
		sd_log_err("Cannot allocate enough memory for the creation of the hash table!\n");
		SD_ASSERT(false);
		return SD_RET_ALLOCATE_MEMEORY;
	}
	(*table)->ma = ma;
	(*table)->keysize = keysize;
	(*table)->elemsize = elemsize;
	(*table)->hash_func = hash_func;
	(*table)->key_cmp = key_cmp;
	(*table)->hashsize = hashsize;
	for (i=0;i<hashsize; i++)
	{
		/* Notice: in this block list, each element should contain the both key and data part */
		ret = sd_SyncBlistCreate(&(*table)->buckets[i], keysize+elemsize, bucketNodeSize, ma);
		SD_ON_FAILED_RETURN(ret);
	}
	return SD_RET_OK;
}

sd_Ret sd_SyncHashtableFree(sd_SyncHashtable* table)
{
	uint32 i;
	struct sd_MemoryAllocator* ma = table->ma;
	for (i=0;i<table->hashsize;i++)
	{
		sd_SyncBlistFree(&table->buckets[i]);
	}
	ma->deallocate(table);
	return SD_RET_OK;
}

sd_Ret sd_SyncHashtableInsert(sd_SyncHashtable* table, const void* key, const void* elem)
{
	sd_Ret ret;
	uint32 hashcode = table->hash_func(key);
	uint32 hashindex = hashcode % table->hashsize;

	ret = sd_SyncBlistInsertTogether(&table->buckets[hashindex], key, table->keysize, elem);
	SD_ON_FAILED_RETURN(ret);

	return SD_RET_OK;
}

sd_Ret sd_SyncHashtableLookup(sd_SyncHashtable* table, const void* key, void* elem)
{
	sd_Key k;
	uint32 hashcode = table->hash_func(key);
	uint32 hashindex = hashcode % table->hashsize;
	sd_HashKeyCompare key_cmp = table->key_cmp;
	sd_SyncBlistIterator iter;

	sd_SyncBlistBegin(&table->buckets[hashindex], &iter, false, false);
	while(sd_SyncBlistNext(&iter) != SD_RET_FALSE)
	{
		sd_SyncBlistPartialGet(&iter, &k, 0, table->keysize);
		if (key_cmp(key, &k) == 0)
		{
			if (elem != NULL)
			{
				/* The content field's offset is started after the 'Key' field */
				sd_SyncBlistPartialGet(&iter, elem, (int)table->keysize, table->elemsize);
			}
			/* Release the lock of current node in the iterator */
			sd_SyncBlistReleaseLock(&iter);
			return SD_RET_OK;
		}
	}
	/* Release the lock of current node in the iterator */
	sd_SyncBlistReleaseLock(&iter);
	return SD_RET_KEY_NOTFOUND;
}

sd_Ret sd_SyncHashtableLookupWithLock(sd_SyncHashtable* table, const void* key, sd_SyncHashtableIterator* iter, bool bLockList)
{
	sd_Key k;
	uint32 hashcode = table->hash_func(key);
	uint32 hashindex = hashcode % table->hashsize;
	sd_HashKeyCompare key_cmp = table->key_cmp;
	sd_SyncBlistIterator biter;

	sd_SyncBlistBegin(&table->buckets[hashindex], &biter, true, bLockList);
	while(sd_SyncBlistNext(&biter) != SD_RET_FALSE)
	{
		sd_SyncBlistPartialGet(&biter, &k, 0, table->keysize);
		if (key_cmp(key, &k) == 0)
		{			
			if (iter != NULL)
			{
				iter->bucketIndex = hashindex;
				iter->bucketIter = biter;
				iter->hashtable = table;
				iter->bWLock = true;
			}
			return SD_RET_OK;
		}
	}
	if (iter != NULL)
	{
		iter->bucketIndex = hashindex;
		iter->bucketIter = biter;
		iter->hashtable = table;
		iter->bWLock = true;
	}
	return SD_RET_KEY_NOTFOUND;
}

sd_Ret sd_SyncHashtableInsertWithLock(sd_SyncHashtableIterator* iter, const void* key, const void* elem)
{
	return sd_SyncBlistInsertTogetherWithoutLock(iter->bucketIter.blist, key, iter->hashtable->keysize, elem);
}

sd_Ret sd_SyncHashtableReleaseLock(sd_SyncHashtableIterator* iter)
{
	return sd_SyncBlistReleaseLock(&iter->bucketIter);
}

sd_Ret sd_SyncHashtableUpdate(sd_SyncHashtable* table, const void* key, const void* elem)
{
	sd_Key k;
	uint32 hashcode = table->hash_func(key);
	uint32 hashindex = hashcode % table->hashsize;
	sd_HashKeyCompare key_cmp = table->key_cmp;
	sd_SyncBlistIterator iter;

	sd_SyncBlistBegin(&table->buckets[hashindex], &iter, true, false);
	while(sd_SyncBlistNext(&iter) != SD_RET_FALSE)
	{
		sd_SyncBlistPartialGet(&iter, &k, 0, table->keysize);
		if (key_cmp(key, &k) == 0)
		{
			sd_SyncBlistUpdateTogether(&iter, key, table->keysize, elem);
			sd_SyncBlistReleaseLock(&iter);
			return SD_RET_OK;
		}
	}
	sd_SyncBlistReleaseLock(&iter);
	return SD_RET_KEY_NOTFOUND;
}

sd_Ret sd_SyncHashtableDelete(sd_SyncHashtable* table, const void* key)
{
	sd_Key k;
	uint32 hashcode = table->hash_func(key);
	uint32 hashindex = hashcode % table->hashsize;
	sd_HashKeyCompare key_cmp = table->key_cmp;
	sd_SyncBlistIterator iter;

	sd_SyncBlistBegin(&table->buckets[hashindex], &iter, true, false);
	while(sd_SyncBlistNext(&iter) != SD_RET_FALSE)
	{
		sd_SyncBlistPartialGet(&iter, &k, 0, table->keysize);
		if (key_cmp(key, &k) == 0)
		{
			sd_SyncBlistRemoveAndReleaseLock(&iter);
			sd_SyncBlistReleaseLock(&iter);

			return SD_RET_OK;
		}
	}
	sd_SyncBlistReleaseLock(&iter);
	return SD_RET_KEY_NOTFOUND;
}

sd_Ret sd_SyncHashtableRemoveAndReleaseLock(sd_SyncHashtableIterator* iter)
{
	sd_SyncBlistRemoveAndReleaseLock(&iter->bucketIter);
	return SD_RET_OK;
}

sd_Ret sd_SyncHashtableClear(sd_SyncHashtable* table)
{
	uint32 i;
	for (i=0;i<table->hashsize; i++)
	{
		sd_SyncBlistClear(&table->buckets[i]);
	}
	return SD_RET_OK;
}

sd_Ret sd_SyncHashtableBegin(sd_SyncHashtable* table, sd_SyncHashtableIterator* iter, bool bWLock)
{
	iter->hashtable = table;
	iter->bucketIndex = -1;
	iter->bWLock = bWLock;
	return SD_RET_OK;
}

sd_Ret sd_SyncHashtableNext(sd_SyncHashtableIterator* iter)
{
	sd_Ret ret;
	if (iter->bucketIndex == -1)
	{
		iter->bucketIndex = 0;
		if (iter->hashtable->hashsize == 0)
			return SD_RET_FALSE;
		sd_SyncBlistBegin(&iter->hashtable->buckets[0], &iter->bucketIter, iter->bWLock, false);
	}
	ret = sd_SyncBlistNext(&iter->bucketIter);
	while (ret == SD_RET_FALSE)
	{
		if ((size_t)iter->bucketIndex == iter->hashtable->hashsize -1)
		{
			/* No more list to iterate*/
			return SD_RET_FALSE;
		}
		else
		{
			sd_SyncBlistReleaseLock(&iter->bucketIter);
			/* Jump to the next list */
			iter->bucketIndex++;
			sd_SyncBlistBegin(&iter->hashtable->buckets[iter->bucketIndex], &iter->bucketIter, iter->bWLock, false);
			ret = sd_SyncBlistNext(&iter->bucketIter);
		}
	}
	return ret;
}

sd_Ret sd_SyncHashtableGet(sd_SyncHashtableIterator* iter, void* elem)
{
	return sd_SyncBlistPartialGet(&iter->bucketIter, elem, (int)iter->hashtable->keysize, iter->hashtable->elemsize);
}

sd_Ret sd_SyncHashtableGetKey(sd_SyncHashtableIterator* iter, void* elem)
{
	return sd_SyncBlistPartialGet(&iter->bucketIter, elem, 0, iter->hashtable->keysize);
}

